<template>
  <div class="parent">
    <div id="container"></div>
    <span id="info"></span>
  </div>
  
</template>

<script setup lang="ts">
import * as THREE from "three";
import { onMounted } from "vue";

function makeCamera(fov = 40) {
  const aspect = 2;
  const zNear = 0.1;
  const zFar = 1000;
  return new THREE.PerspectiveCamera(fov, aspect, zNear, zFar);
}

onMounted(() => {
  const scene = new THREE.Scene();
  const camera = makeCamera();
  camera.position.set(8, 4, 10).multiplyScalar(3); //最终位置是 24 12 30
  camera.lookAt(0, 0, 0);

  const renderer = new THREE.WebGLRenderer();
  document.getElementById("container")?.appendChild(renderer.domElement);
  renderer.setSize(window.innerWidth, window.innerHeight);
  renderer.setClearColor(0xaaaaaa);
  renderer.shadowMap.enabled = true;

  {
    const light = new THREE.DirectionalLight(0xffffff, 3);
    light.position.set(0, 20, 0);
    scene.add(light);
    light.castShadow = true; //开启阴影能力
    light.shadow.mapSize.width = 2048;
    light.shadow.mapSize.height = 2048;

    const d = 50;
    light.shadow.camera.left = -d;
    light.shadow.camera.right = d;
    light.shadow.camera.top = d;
    light.shadow.camera.bottom = -d;
    light.shadow.camera.near = 1;
    light.shadow.camera.far = 50;
    light.shadow.bias = 0.001;
  }

  {
    const light = new THREE.DirectionalLight(0xffffff, 3);
    light.position.set(1, 2, 4);
    scene.add(light);
  }

  //地面
  const groundGeometry = new THREE.PlaneGeometry(50, 50);
  const groundMaterial = new THREE.MeshPhongMaterial({ color: 0xcc8866 });
  const groundMesh = new THREE.Mesh(groundGeometry, groundMaterial);
  groundMesh.rotation.x = Math.PI * -0.5;
  groundMesh.receiveShadow = true;
  scene.add(groundMesh);

  const carWidth = 4;
  const carHeight = 1;
  const carLength = 8;

  const tank = new THREE.Object3D();
  scene.add(tank);

  //坦克主体
  const bodyGeometry = new THREE.BoxGeometry(carWidth, carHeight, carLength);
  const bodyMaterial = new THREE.MeshPhongMaterial({ color: 0x6688aa });
  const bodyMesh = new THREE.Mesh(bodyGeometry, bodyMaterial);
  bodyMesh.position.y = 1.4;
  bodyMesh.castShadow = true;
  tank.add(bodyMesh);

  //坦克摄像机
  const tankCameraFov = 75;
  const tankCamera = makeCamera(tankCameraFov);
  tankCamera.position.y = 3;
  tankCamera.position.z = -6;
  tankCamera.rotation.y = Math.PI;
  bodyMesh.add(tankCamera);

  //创建四个轮子
  const wheelRadius = 1;
  const wheelThickness = 0.5;
  const wheelSegments = 6;
  const wheelGeometry = new THREE.CylinderGeometry(
    wheelRadius,
    wheelRadius,
    wheelThickness,
    wheelSegments
  );
  const wheelMaterial = new THREE.MeshPhongMaterial({ color: 0x888888 });
  const wheelPositions: [number, number, number][] = [
    [-carWidth / 2 - wheelThickness / 2, -carHeight / 2, carLength / 3],
    [carWidth / 2 + wheelThickness / 2, -carHeight / 2, carLength / 3],
    [-carWidth / 2 - wheelThickness / 2, -carHeight / 2, 0],
    [carWidth / 2 + wheelThickness / 2, -carHeight / 2, 0],
    [-carWidth / 2 - wheelThickness / 2, -carHeight / 2, -carLength / 3],
    [carWidth / 2 + wheelThickness / 2, -carHeight / 2, -carLength / 3],
  ];
  const wheelMeshes = wheelPositions.map((pos) => {
    const mesh = new THREE.Mesh(wheelGeometry, wheelMaterial);
    mesh.position.set(...pos);
    mesh.rotation.z = Math.PI / 2;
    mesh.castShadow = true;
    bodyMesh.add(mesh);
    return mesh;
  });

  //上面的盖子
  const domeRadius = 2;
  const domeWidthSubdivisions = 12;
  const domeHeightSubdivisions = 12;
  const domePhiStart = 0;
  const domePhiEnd = Math.PI * 2;
  const domeThetaStart = 0;
  const domeThetaEnd = Math.PI * 0.5;
  const domeGeometry = new THREE.SphereGeometry(
    domeRadius,
    domeWidthSubdivisions,
    domeHeightSubdivisions,
    domePhiStart,
    domePhiEnd,
    domeThetaStart,
    domeThetaEnd
  );
  const domeMesh = new THREE.Mesh(domeGeometry, bodyMaterial);
  domeMesh.castShadow = true;
  bodyMesh.add(domeMesh);
  domeMesh.position.y = 0.5;

  //炮塔
  const turretWidth = 0.1;
  const turretHeight = 0.1;
  const turretLength = carLength * 0.75 * 0.2;
  const turretGeometry = new THREE.BoxGeometry(
    turretWidth,
    turretHeight,
    turretLength
  );
  const turretMesh = new THREE.Mesh(turretGeometry, bodyMaterial);
  const turretPivot = new THREE.Object3D();
  turretMesh.castShadow = true;
  turretPivot.scale.set(5, 5, 5);
  turretPivot.position.y = 0.5;
  turretMesh.position.z = turretLength * 0.5;
  turretPivot.add(turretMesh);
  bodyMesh.add(turretPivot);

  //炮塔摄像头
  const turretCamera = makeCamera();
  turretCamera.position.y = 0.75 * 0.2;
  turretMesh.add(turretCamera);

  //目标物
  const targetGeometry = new THREE.SphereGeometry(0.5, 6, 3);
  const targetMaterial = new THREE.MeshPhongMaterial({
    color: 0x00ff00,
    flatShading: true,
  });
  const targetMesh = new THREE.Mesh(targetGeometry, targetMaterial);
  const targetOrbit = new THREE.Object3D();
  const targetElevation = new THREE.Object3D();
  const targetBob = new THREE.Object3D();
  targetMesh.castShadow = true;
  scene.add(targetOrbit);
  targetOrbit.add(targetElevation);
  targetElevation.position.z = carLength * 2;
  targetElevation.position.y = 8;
  targetElevation.add(targetBob);
  targetBob.add(targetMesh);

  //目标物摄像头
  const targetCamera = makeCamera();
  const targetCameraPivot = new THREE.Object3D();
  targetCamera.position.y = 1;
  targetCamera.position.z = -2;
  targetCamera.rotation.y = Math.PI;
  targetBob.add(targetCameraPivot);
  targetCameraPivot.add(targetCamera);

  //行进轨迹
  const curve = new THREE.SplineCurve([
    new THREE.Vector2(-10, 0),
    new THREE.Vector2(-5, 5),
    new THREE.Vector2(0, 0),
    new THREE.Vector2(5, -5),
    new THREE.Vector2(10, 0),
    new THREE.Vector2(5, 10),
    new THREE.Vector2(-5, 10),
    new THREE.Vector2(-10, -10),
    new THREE.Vector2(-15, -8),
    new THREE.Vector2(-10, 0),
  ]);

  const points = curve.getPoints(50);
  const geometry = new THREE.BufferGeometry().setFromPoints(points);
  const material = new THREE.LineBasicMaterial({ color: 0xff0000 });
  const splineObject = new THREE.Line(geometry, material);
  splineObject.rotation.x = Math.PI * 0.5;
  splineObject.position.y = 0.05;
  scene.add(splineObject);

  const resizeRendererToDisplaySize = (renderer: any) => {
    const canvas = renderer.domElement;
    const width = canvas.clientWidth;
    const height = canvas.clientHeight;
    const needResize = canvas.width !== width || canvas.height !== height;
    if (needResize) {
      renderer.setSize(width, height, false);
    }

    return needResize;
  };

  const targetPosition = new THREE.Vector3();
	const tankPosition = new THREE.Vector2();
	const tankTarget = new THREE.Vector2();

  const cameras = [
		{ cam: camera, desc: 'detached camera', },
		{ cam: turretCamera, desc: 'on turret looking at target', },
		{ cam: targetCamera, desc: 'near target looking at tank', },
		{ cam: tankCamera, desc: 'above back of tank', },
	];

  const infoElem = document.querySelector( '#info' );

  const render = (time: any) => {

		time *= 0.001;

		if ( resizeRendererToDisplaySize( renderer ) ) {

			const canvas = renderer.domElement;
			cameras.forEach( ( cameraInfo ) => {

				const camera = cameraInfo.cam;
				camera.aspect = canvas.clientWidth / canvas.clientHeight;
				camera.updateProjectionMatrix();

			} );

		}

		// move target
		targetOrbit.rotation.y = time * .27;
		targetBob.position.y = Math.sin( time * 2 ) * 4;
		targetMesh.rotation.x = time * 7;
		targetMesh.rotation.y = time * 13;
		targetMaterial.emissive.setHSL( time * 10 % 1, 1, .25 );
		targetMaterial.color.setHSL( time * 10 % 1, 1, .25 );

		// move tank
		const tankTime = time * .05;
		curve.getPointAt( tankTime % 1, tankPosition );
		curve.getPointAt( ( tankTime + 0.01 ) % 1, tankTarget );
		tank.position.set( tankPosition.x, 0, tankPosition.y );
		tank.lookAt( tankTarget.x, 0, tankTarget.y );

		// face turret at target
		targetMesh.getWorldPosition( targetPosition );
		turretPivot.lookAt( targetPosition );

		// make the turretCamera look at target
		turretCamera.lookAt( targetPosition );

		// make the targetCameraPivot look at the at the tank
		tank.getWorldPosition( targetPosition );
		targetCameraPivot.lookAt( targetPosition );

		wheelMeshes.forEach( ( obj ) => {

			obj.rotation.x = time * 3;

		} );

		const camera = cameras[ time * .25 % cameras.length | 0 ];
		infoElem!!.textContent = camera.desc;

		renderer.render( scene, camera.cam );

		requestAnimationFrame( render );
  };
  requestAnimationFrame( render );
});
</script>

<style scoped>
.parent {
  position: relative;
  width: 100%;
  height: 100%;
}

.parent > * {
  position: absolute;
  top: 0;
  left: 0;
}
</style>
